function createPatchFunction(backend) {

    var i, j;

    // cbs={ destory:[],actived:[],create:[] }
    var cbs = {};

    // platformModules.concat(baseModules)
    // platformModules = [attrs,klass,events,domProps,style]
    // baseModules = [ ref, directives ]
    var modules = backend.modules;
    var nodeOps = backend.nodeOps;

    // hooks = ['create', 'activate', 'update', 'remove', 'destroy']
    for (i = 0; i < hooks.length; ++i) {

        var current_hook = hooks[i] 
        var cb_hook = cbs[current_hook] = [];
        // modules=[events]
        // event = { create(){} , destory(){} }
        // 比如现在 current_hook 是 create，然后从 moudule 的events 中拿 create 的函数
        for (j = 0; j < modules.length; ++j) {

            // 比如是 events
            var _module = modules[j]

            // 获取 module 中 的 destory, create,remove 等
            var module_hook = _module[ current_hook ]

            if (isDef(module_hook)) {

                // cbs={ destory:[] }
                cb_hook.push(module_hook);

            }
        }
    }

    // 把 DOM 节点注册为 Vnode
    function emptyNodeAt(elm) {
        return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
    }

    function createRmCb(childElm, listeners) {
        function remove() {
            if (--remove.listeners === 0) {
                removeNode(childElm);
            }
        }
        remove.listeners = listeners;
        return remove
    }

    function removeNode(el) {
        var parent = nodeOps.parentNode(el);

        // element may have already been removed due to v-html / v-text
        if (isDef(parent)) {
            nodeOps.removeChild(parent, el);
        }
    }

    function isUnknownElement$$1(vnode, inVPre) {
        return (
            !inVPre && !vnode.ns &&
            !(
                config.ignoredElements.length &&
                config.ignoredElements.some(function(ignore) {
                    return isRegExp(ignore) ?
                        ignore.test(vnode.tag) :
                        ignore === vnode.tag
                })
            ) &&
            config.isUnknownElement(vnode.tag)
        )
    }

    var creatingElmInVPre = 0;

    function createElm(
        vnode,insertedVnodeQueue,parentElm,
        refElm,nested,ownerArray,index
    ) {

        if (isDef(vnode.elm) && isDef(ownerArray)) {

            // This vnode was used in a previous render!
            // now it's used as a new node, overwriting its elm would cause
            // potential patch errors down the road when it's used as an insertion
            // reference node. Instead, we clone the node on-demand before creating
            // associated DOM element for it.
            vnode = ownerArray[index] = cloneVNode(vnode);
        }

        vnode.isRootInsert = !nested; // for transition enter check
        
        /***
         * 如果是 组件，那么就 返回
         * 1、创建 vm 实例，从另一个维度去 挂载 dom 到父节点
         * 2、因为已经创建了 dom，所以
         */
        if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) return

        var data = vnode.data;
        var children = vnode.children;
        var tag = vnode.tag;

        if (isDef(tag)) {

            if (data && data.pre) {
                creatingElmInVPre++;
            }
            
            // ns 是什么？？？
            vnode.elm = nodeOps.createElement(tag, vnode);
            

            // 先把 子节点插入 vnode.elm，然后再把 vnode.elm 插入parent
            createChildren(vnode, children, insertedVnodeQueue);

            if (isDef(data)) {
                invokeCreateHooks(vnode, insertedVnodeQueue);
            }
            //  插入DOM 节点
            insert(parentElm, vnode.elm, refElm);
                
            if ("development" !== 'production' && data && data.pre) {
                creatingElmInVPre--;
            }
        } 
        else if (isTrue(vnode.isComment)) {
            vnode.elm = nodeOps.createComment(vnode.text);
            insert(parentElm, vnode.elm, refElm);
        } 
        else {
            vnode.elm = nodeOps.createTextNode(vnode.text);
            insert(parentElm, vnode.elm, refElm);
        }
    }

    // 创建子组件的 vm 实例
    function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {

        var i = vnode.data;

        if (isDef(i)) {

            var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;

            // 调用子组件的 init 方法, init 方法就是 Vue.prototype._init
            // 创建子组件的 vm 实例
            if (isDef(i = i.hook) && isDef(i = i.init)) {
                i(vnode, false /* hydrating */ , parentElm, refElm);
            }

            // after calling the init hook, if the vnode is a child component
            // it should've created a child instance and mounted it. the child
            // component also has set the placeholder vnode's elm.
            // in that case we can just return the element and be done.
            if (isDef(vnode.componentInstance)) {
                initComponent(vnode, insertedVnodeQueue);
                if (isTrue(isReactivated)) {
                    reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
                }
                return true
            }
        }
    }

    function initComponent(vnode, insertedVnodeQueue) {

        if (isPatchable(vnode)) {
            invokeCreateHooks(vnode, insertedVnodeQueue);
        }
    }

    function reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm) {

        var i;

        // hack for #4339: a reactivated component with inner transition
        // does not trigger because the inner node's created hooks are not called
        // again. It's not ideal to involve module-specific logic in here but
        // there doesn't seem to be a better way to do it.
        var innerNode = vnode;

        while (innerNode.componentInstance) {

            innerNode = innerNode.componentInstance._vnode;

            if (isDef(i = innerNode.data) && isDef(i = i.transition)) {
                for (i = 0; i < cbs.activate.length; ++i) {
                    cbs.activate[i](emptyNode, innerNode);
                }
                insertedVnodeQueue.push(innerNode);
                break
            }
        }
        // unlike a newly created component,
        // a reactivated keep-alive component doesn't insert itself
        insert(parentElm, vnode.elm, refElm);
    }

    /**
     * 插入节点
     * 1、无兄弟节点，直接插入父节点内
     * 2、有兄弟，插在兄弟前面
     */
    //  插入节点
    function insert(parent, elm, ref$$1) {

        if (isDef(parent)) {
            if (isDef(ref$$1)) {
                if (ref$$1.parentNode === parent) {
                    nodeOps.insertBefore(parent, elm, ref$$1);
                }
            } else {
                nodeOps.appendChild(parent, elm);
            }
        }
    }

    /***
     * 创建子节点，如果子节点是数组，则遍历执行 createElm 方法
     * 如果子节点的 text 属性有数据，则使用 nodeOps.appendChild(...) 在真实 DOM 中插入文本内容
     */
    function createChildren(vnode, children, insertedVnodeQueue) {

        if (Array.isArray(children)) {

            checkDuplicateKeys(children);

            for (var i = 0; i < children.length; ++i) {
                createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i);
            }

        } else if (isPrimitive(vnode.text)) {

            nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)));
        }
    }

    function isPatchable(vnode) {
        while (vnode.componentInstance) {
            vnode = vnode.componentInstance._vnode;
        }
        return isDef(vnode.tag)
    }

    function invokeCreateHooks(vnode, insertedVnodeQueue) {

        // create: updateAttrs updateClass updateDOMListeners updateDOMProps updateStyle create updateDirectives
        for (var b = 0; b < cbs.create.length; ++b) {
            cbs.create[b](emptyNode, vnode);
        }

        i = vnode.data.hook; // Reuse variable

        if (isDef(i)) {
            if (isDef(i.create)) { i.create(emptyNode, vnode); }
            if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
        }
    }

    //  批量调用 createElm 新建节点
    function addVnodes(
        parentElm, refElm, vnodes, 
        startIdx, endIdx, insertedVnodeQueue
    ) {

        for (; startIdx <= endIdx; ++startIdx) {
            createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx);
        }
    }

    function invokeDestroyHook(vnode) {

        var i, j;
        var data = vnode.data;

        if (isDef(data)) {
            
            //  data.hook.destory 也是一个迷
            if (isDef(i = data.hook) && isDef(i = i.destroy)) { 
                
                i(vnode); 
            }
            
            // 这里的 cbs.destory 是个迷
            for (i = 0; i < cbs.destroy.length; ++i) { 
                cbs.destroy[i](vnode);
            }
        }

        if (isDef(i = vnode.children)) {

            for (j = 0; j < vnode.children.length; ++j) {
                invokeDestroyHook(vnode.children[j]);
            }
        }
    }

    // 会移除 头和 尾 
    function removeVnodes(parentElm, vnodes, startIdx, endIdx) {

        for (; startIdx <= endIdx; ++startIdx) {

            var ch = vnodes[startIdx];

            if (isDef(ch)) {

                // ch.tag 存在说明是 vue 组件，不是原生dom 节点
                if (isDef(ch.tag)) {
                    removeAndInvokeRemoveHook(ch);
                    invokeDestroyHook(ch);
                } else { // Text node
                    removeNode(ch.elm);
                }
            }
        }
    }

    function removeAndInvokeRemoveHook(vnode, rm) {

        if (isDef(rm) || isDef(vnode.data)) {

            var i;
            var listeners = cbs.remove.length + 1;
            
            if (isDef(rm)) {
                // we have a recursively passed down rm callback
                // increase the listeners count
                rm.listeners += listeners;
            } else {
                // directly removing
                rm = createRmCb(vnode.elm, listeners);
            }

            // recursively invoke hooks on child component root node
            if (isDef(i = vnode.componentInstance) && isDef(i = i._vnode) && isDef(i.data)) {
                removeAndInvokeRemoveHook(i, rm);
            }

            for (i = 0; i < cbs.remove.length; ++i) {
                cbs.remove[i](vnode, rm);
            }

            if (isDef(i = vnode.data.hook) && isDef(i = i.remove)) {
                i(vnode, rm);
            } else {
                rm();
            }
        } else {
            removeNode(vnode.elm);
        }
    }

    /***
     * 一个 while 循环 比较更新，一个 for 遍历添加
     */
    function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {

        var oldStartIdx = 0;
        var newStartIdx = 0;

        var oldEndIdx = oldCh.length - 1;
        var oldStartVnode = oldCh[0];
        var oldEndVnode = oldCh[oldEndIdx];

        var newEndIdx = newCh.length - 1;
        var newStartVnode = newCh[0];
        var newEndVnode = newCh[newEndIdx];

        var oldKeyToIdx, idxInOld, vnodeToMove, refElm;

        // removeOnly is a special flag used only by <transition-group>
        // to ensure removed elements stay in correct relative positions
        // during leaving transitions
        var canMove = !removeOnly;

        checkDuplicateKeys(newCh);

        // old 和 new 先 头比头，尾比尾，头比尾，尾比头，实在不行，遍历找相同Vnode
        // 不断地更新 OldIndex 和 OldVnode ，newIndex 和 newVnode
        while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {

            // 先把 oldStartVnode 和 oldEndVnode 变成 有值

            // 只有  oldStartIdx  变化
            if (isUndef(oldStartVnode)) {
                oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
            } 
            
            // 只有  oldEndIdx  变化
            else if (isUndef(oldEndVnode)) {
                oldEndVnode = oldCh[--oldEndIdx];
            } 
            
            //  newStartIdx 、 oldStartIdx  变化
            else if (sameVnode(oldStartVnode, newStartVnode)) {
                
                patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);

                oldStartVnode = oldCh[++oldStartIdx];
                newStartVnode = newCh[++newStartIdx];
            } 
            
            //  newEndIdx 、 oldEndIdx  变化
            else if (sameVnode(oldEndVnode, newEndVnode)) {

                patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);

                oldEndVnode = oldCh[--oldEndIdx];
                newEndVnode = newCh[--newEndIdx];

            } 
            
            /**
             * 头比头，尾比尾  和  头比尾，尾比头的区别是，
             * 头比尾，尾比头 的时候，会进行 dom 插入操作，而 头头，尾尾单纯只是比较更新，不会移动dom
             */
            //  newEndIdx  、 oldStartIdx  变化
            else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right

                patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);

                // oldStartVnode 放到 oldEndVnode 后面，还要找到 oldEndValue 后面的节点
                canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));

                oldStartVnode = oldCh[++oldStartIdx];
                newEndVnode = newCh[--newEndIdx];
            } 
            
            //  newStartIdx 、 oldEndIdx  变化
            else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left

                patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);

                // oldEndVnode 放到 oldStartVnode 前面
                canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);

                oldEndVnode = oldCh[--oldEndIdx];
                newStartVnode = newCh[++newStartIdx];
            }
            
            // 只有  newStartIdx  变化
            else {

                // oldKeyToIdx 是一个 把 Vnode 的 key 和 index 转换的 map
                if (isUndef(oldKeyToIdx)) { 
                    oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); 
                }

                // 使用 newStartVnode 去 OldMap 中寻找 相同节点
                idxInOld = 
                    isDef(newStartVnode.key) 
                    ? oldKeyToIdx[newStartVnode.key] 

                    // 孩子一个个比对，如果 newStartVnode 和 旧孩子某个一样，就返回 该旧孩子的 index
                    : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
                
                //  新孩子中，存在一个新节点，老节点中没有
                if (isUndef(idxInOld)) { // New element
                    
                    // createElm 什么作用，需要查看
                    //  把  newStartVnode 插入 oldStartVnode 的前面
                    createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
                } 
                
                else {
                    
                    //  找到 oldCh 中 和 newStartVnode 一样的节点
                    vnodeToMove = oldCh[idxInOld];

                    if (sameVnode(vnodeToMove, newStartVnode)) {

                        patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
                        
                        // 删除这个 index
                        oldCh[idxInOld] = undefined;

                        // 把 vnodeToMove 移动到  oldStartVnode 前面
                        canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
                    } 
                    
                    // 只能创建一个新节点插入到 parentElm 的子节点中
                    else {

                        // same key but different element. treat as new element
                        createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
                    }
                }

                // 更新 newStartIdx
                newStartVnode = newCh[++newStartIdx];
            }
        }

        /***
         * 很多种情况
         * 
         * 1、oldCh 存在，但是 节点全部一样，那么 oldStartIdx 和 oldEndIdx  没有变化，
         *    newCh 逐个 跟 oldCh 比较，因为不一一样，所有逐个添加进了parent
         */
        // 说明老节点比对完了，但是新节点还有多的，需要把新节点
        if (oldStartIdx > oldEndIdx) {

            // ？：newCh 中的 elm 绑定过了dom 了吗
            refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm;

            addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue);
        } 
        
        // 说明新节点比对完了，老节点还有多，需要删除多余的 老节点
        // 一种情况是 oldCh 和 newCh 全不一样，newStartIdx 一直变化，一直把 newStartNode 添加进去，然后需要移除 oldCh
        else if (newStartIdx > newEndIdx) {
            removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
        }
    }

    function checkDuplicateKeys(children) {

        var seenKeys = {};

        for (var i = 0; i < children.length; i++) {

            var vnode = children[i];
            var key = vnode.key;

            if (isDef(key)) {
                if (seenKeys[key]) {
                    warn(
                        ("Duplicate keys detected: '" + key + "'. This may cause an update error."),
                        vnode.context
                    );
                } else {
                    seenKeys[key] = true;
                }
            }
        }
    }

    function findIdxInOld(node, oldCh, start, end) {

        for (var i = start; i < end; i++) {
            var c = oldCh[i];
            if (isDef(c) && sameVnode(node, c)) { return i }
        }
    }

    // insertedVnodeQueue 不知道是什么
    // 作用感觉是 比较两个 Vnode 的 children
    function patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly) {

        // 更新
        if (oldVnode === vnode) return

        var elm = vnode.elm = oldVnode.elm;
        
        if (isTrue(oldVnode.isAsyncPlaceholder)) {

            if (isDef(vnode.asyncFactory.resolved)) {
                hydrate(oldVnode.elm, vnode, insertedVnodeQueue);
            } else {
                vnode.isAsyncPlaceholder = true;
            }
            return
        }

        // reuse element for static trees.
        // note we only do this if the vnode is cloned -
        // if the new node is not cloned it means the render functions have been
        // reset by the hot-reload-api and we need to do a proper re-render.
        if (
            isTrue(vnode.isStatic) &&
            isTrue(oldVnode.isStatic) &&
            vnode.key === oldVnode.key &&
            (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))
        ) {
            vnode.componentInstance = oldVnode.componentInstance;
            return
        }

        var i;
        var data = vnode.data;

        if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
            i(oldVnode, vnode);
        }

        var oldCh = oldVnode.children;
        var ch = vnode.children;

        if (isDef(data) && isPatchable(vnode)) {

            for (i = 0; i < cbs.update.length; ++i) { var name=cbs.update[i]; name(oldVnode, vnode); }
            if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }
        }

        // 更新children
        if (isUndef(vnode.text)) {

            // 存在 oldCh 和 ch 时
            if (isDef(oldCh) && isDef(ch)) {

                // 更新
                if (oldCh !== ch) { 
                    updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); 
                }
            } 
            
            // 存在 newCh 时，oldCh 只能是不存在，如果存在，就跳到上面的条件了
            else if (isDef(ch)) {

                if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); }
                
                // 更新
                addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);
            } 
            
            else if (isDef(oldCh)) {

                // 更新
                removeVnodes(elm, oldCh, 0, oldCh.length - 1);
            } 
            
            else if (isDef(oldVnode.text)) {

                // 更新
                nodeOps.setTextContent(elm, '');
            }
        } 
        
        else if (oldVnode.text !== vnode.text) {
            // 更新
            nodeOps.setTextContent(elm, vnode.text);
        }

        // 执行 postpatch 和 指令的 componentUpdated
        if (isDef(data)) {
            if (isDef(i = data.hook) && isDef(i = i.postpatch)) { 
                i(oldVnode, vnode); 
            }
        }
    }

    function invokeInsertHook(vnode, queue, initial) {

        // delay insert hooks for component root nodes, invoke them after the
        // element is really inserted
        if (isTrue(initial) && isDef(vnode.parent)) {

            vnode.parent.data.pendingInsert = queue;

        } else {
            for (var i = 0; i < queue.length; ++i) {
                queue[i].data.hook.insert(queue[i]);
            }
        }
    }

    var hydrationBailed = false;

    // list of modules that can skip create hook during hydration because they
    // are already rendered on the client or has no need for initialization
    // Note: style is excluded because it relies on initial clone for future
    // deep updates (#7063).
    var isRenderedModule = makeMap('attrs,class,staticClass,staticStyle,key');

    // Note: this is a browser-only function so we can assume elms are DOM nodes.
    function hydrate(elm, vnode, insertedVnodeQueue, inVPre) {

        var i;
        var tag = vnode.tag;
        var data = vnode.data;
        var children = vnode.children;
        inVPre = inVPre || (data && data.pre);
        vnode.elm = elm;

        if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
            vnode.isAsyncPlaceholder = true;
            return true
        }

        // assert node match
        {
            if (!assertNodeMatch(elm, vnode, inVPre)) {
                return false
            }
        }

        if (isDef(data)) {
            if (isDef(i = data.hook) && isDef(i = i.init)) { i(vnode, true /* hydrating */ ); }
            if (isDef(i = vnode.componentInstance)) {
                // child component. it should have hydrated its own tree.
                initComponent(vnode, insertedVnodeQueue);
                return true
            }
        }

        if (isDef(tag)) {

            if (isDef(children)) {

                // empty element, allow client to pick up and populate children
                if (!elm.hasChildNodes()) {
                    createChildren(vnode, children, insertedVnodeQueue);

                } else {

                    // v-html and domProps: innerHTML
                    if (isDef(i = data) && isDef(i = i.domProps) && isDef(i = i.innerHTML)) {

                        if (i !== elm.innerHTML) {
                            /* istanbul ignore if */
                            if ("development" !== 'production' &&
                                typeof console !== 'undefined' &&
                                !hydrationBailed
                            ) {
                                hydrationBailed = true;
                                console.warn('Parent: ', elm);
                                console.warn('server innerHTML: ', i);
                                console.warn('client innerHTML: ', elm.innerHTML);
                            }
                            return false
                        }

                    } else {

                        // iterate and compare children lists
                        var childrenMatch = true;
                        var childNode = elm.firstChild;

                        for (var i$1 = 0; i$1 < children.length; i$1++) {
                            if (!childNode || !hydrate(childNode, children[i$1], insertedVnodeQueue, inVPre)) {
                                childrenMatch = false;
                                break
                            }
                            childNode = childNode.nextSibling;
                        }

                        // if childNode is not null, it means the actual childNodes list is
                        // longer than the virtual children list.
                        if (!childrenMatch || childNode) {

                            if (
                                "development" !== 'production' &&
                                typeof console !== 'undefined' &&
                                !hydrationBailed
                            ) {
                                hydrationBailed = true;
                                console.warn('Parent: ', elm);
                                console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children);
                            }
                            return false
                        }
                    }
                }
            }

            if (isDef(data)) {

                var fullInvoke = false;

                for (var key in data) {
                    if (!isRenderedModule(key)) {
                        fullInvoke = true;
                        invokeCreateHooks(vnode, insertedVnodeQueue);
                        break
                    }
                }

                if (!fullInvoke && data['class']) {
                    // ensure collecting deps for deep class bindings for future updates
                    traverse(data['class']);
                }
            }

        } else if (elm.data !== vnode.text) {
            elm.data = vnode.text;
        }
        return true
    }

    function assertNodeMatch(node, vnode, inVPre) {

        if (isDef(vnode.tag)) {
            return vnode.tag.indexOf('vue-component') === 0 
                || (!isUnknownElement$$1(vnode, inVPre) 
                &&vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
            )

        } else {
            return node.nodeType === (vnode.isComment ? 8 : 3)
        }
    }

    // invokeDestroyHook,invokeInsertHook,insertedVnodeQueue
    return function patch(oldVnode, vnode, hydrating, removeOnly, parentElm, refElm) {

        //  没有新节点，直接移除旧节点
        if (isUndef(vnode)) {
            if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
            return
        }

        var isInitialPatch = false;
        var insertedVnodeQueue = [];

        // 没有旧节点，直接生成新f节点
        // oldVnode 有可能是原生 dom，当 不是组件时，oldVnode 不是 undefined
        if (isUndef(oldVnode)) {

            // empty mount (likely as component), create new root element
            isInitialPatch = true;
            createElm(vnode, insertedVnodeQueue, parentElm, refElm);

        } 
        
        else {

            /***
             * Vnode 实例没有 nodeType 这个属性，
             *  这是为了区分 dom 节点 和 Vnode 实例
             */
            // console.log(vnode.tag,'isRealElement--->',isRealElement,"sameVnode(oldVnode, vnode)",sameVnode(oldVnode, vnode));

            var isRealElement = isDef(oldVnode.nodeType);

            // 不是 DOM 节点 + 且是一样 Vnode
            if (!isRealElement && sameVnode(oldVnode, vnode)) {

                // patch existing root node
                patchVnode(oldVnode, vnode, insertedVnodeQueue, removeOnly);

            } 
            else {

                // 原生 dom，需要注册为 Vnode 实例
                if (isRealElement) {

                    // mounting to a real element
                    // check if this is server-rendered content and if we can perform
                    // a successful hydration.
                    if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
                        oldVnode.removeAttribute(SSR_ATTR);
                        hydrating = true;
                    }

                    if (isTrue(hydrating)) {

                        if (hydrate(oldVnode, vnode, insertedVnodeQueue)) {
                            invokeInsertHook(vnode, insertedVnodeQueue, true);
                            return oldVnode
                        } else {
                            warn(
                                'The client-side rendered virtual DOM tree is not matching ' +
                                'server-rendered content. This is likely caused by incorrect ' +
                                'HTML markup, for example nesting block-level elements inside ' +
                                '<p>, or missing <tbody>. Bailing hydration and performing ' +
                                'full client-side render.'
                            );
                        }
                    }

                    // either not server-rendered, or hydration failed.
                    // create an empty node and replace it
                    // oldVnode 原先是 dom 节点，注册成为 Vnode 实例
                    oldVnode = emptyNodeAt(oldVnode);
                }

                // replacing existing element
                var oldElm = oldVnode.elm;
                var parentElm$1 = nodeOps.parentNode(oldElm);

                // create new node
                createElm(
                    vnode,
                    insertedVnodeQueue,
                    // extremely rare edge case: do not insert if old element is in a
                    // leaving transition. Only happens when combining transition +
                    // keep-alive + HOCs. (#4590)
                    oldElm._leaveCb ? null : parentElm$1,
                    nodeOps.nextSibling(oldElm)
                );

                // update parent placeholder node element, recursively
                if (isDef(vnode.parent)) {

                    var ancestor = vnode.parent;
                    var patchable = isPatchable(vnode);

                    while (ancestor) {

                        for (var i = 0; i < cbs.destroy.length; ++i) {
                            cbs.destroy[i](ancestor);
                        }

                        ancestor.elm = vnode.elm;

                        if (patchable) {

                            for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
                                cbs.create[i$1](emptyNode, ancestor);
                            }

                            // #6513
                            // invoke insert hooks that may have been merged by create hooks.
                            // e.g. for directives that uses the "inserted" hook.
                            var insert = ancestor.data.hook.insert;

                            if (insert.merged) {
                                // start at index 1 to avoid re-invoking component mounted hook
                                for (var i$2 = 1; i$2 < insert.fns.length; i$2++) {
                                    insert.fns[i$2]();
                                }
                            }

                        } else {
                            registerRef(ancestor);
                        }

                        ancestor = ancestor.parent;
                    }
                }

                // destroy old node
                if (isDef(parentElm$1)) {
                    removeVnodes(parentElm$1, [oldVnode], 0, 0);
                } 
                
                // 探索invokeDestroyHook ，牵扯出 modules， hook
                else if (isDef(oldVnode.tag)) {
                    invokeDestroyHook(oldVnode);
                }
            }
        }

        invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch);

        return vnode.elm
    }
}